import cPickle
import os
import time

class File:
    def __init__(self, path, name, funcMerge=None, funcShouldArchive=None):
        self.name       = name
        self.path       = path
        self.infoname   = self.path + self.name + '.info'
        self.dataname   = self.path + self.name + '.bin'
        self.lockname   = self.path + self.name + '.lock'
        self.tempname   = self.path + self.name + '.temp'
        self.arcname    = self.path + self.name + '.arc'
        self.funcMerge  = funcMerge

    def isLocked(self): return os.path.exists(self.lockname)
    def lock(self):
        while True:
            if self.isLocked():
                time.sleep(1)
                continue
            f = open(self.lockname, 'w')
            f.close()
            break
    def unlock(self): os.unlink(self.lockname)

    def setInfo(self, info):
        if not self.isLocked(): raise RuntimeError('setInfo: file is unlocked')

        f = open(self.infoname, 'wb')
        cPickle.dump(info, f)

    def getInfo(self):
        if not self.isLocked(): raise RuntimeError('getInfo: file is unlocked')
        info = (0, {})
        if os.path.exists(self.infoname):
            f = open(self.infoname, 'rb')
            info = cPickle.load(f)
        return info

    def setData(self, data):
        if not self.isLocked(): raise RuntimeError('setData: file is unlocked')

        f = open(self.dataname, 'wb')
        cPickle.dump(data, f)

    def getData(self):
        if not self.isLocked(): raise RuntimeError('getData: file is unlocked')
        data = []
        if os.path.exists(self.dataname):
            f = open(self.dataname, 'rb')
            data = cPickle.load(f)
        return data

    def setArc(self, data):
        if not self.isLocked(): raise RuntimeError('setArc: file is unlocked')

        f = open(self.arcname, 'wb')
        cPickle.dump(data, f)

    def getArc(self):
        if not self.isLocked(): raise RuntimeError('getArc: file is unlocked')
        data = []
        if os.path.exists(self.arcname):
            f = open(self.arcname, 'rb')
            data = cPickle.load(f)
        return data

    def destroy(self):
        if self.isLocked(): self.unlock()
        self.lock()
        
        try:
            if os.path.exists(self.infoname): os.unlink(self.infoname)
            if os.path.exists(self.dataname): os.unlink(self.dataname)
            if os.path.exists(self.tempname): os.unlink(self.tempname)
        finally:
            self.unlock()

    def reset(self):
        self.lock()

        try:
            self.txn    = 0
            self.setInfo((self.txn, self.meta))
        finally:
            self.unlock()

    def open(self):
        self.lock()

        try:
            info = self.getInfo()
            self.txn    = info[0]
            self.meta   = info[1]

        finally:
            self.unlock()

    def load(self):
        self.lock()

        try:
            info = self.getInfo()
            self.txn    = info[0]
            self.meta   = info[1]

            data = self.getData()
            return data

        finally:
            self.unlock()

    def save(self, data):
        self.lock()

        try:
            info = self.getInfo()
            if info[0] < self.txn: raise RuntimeError('save: current txn(%d) is less than self.txn(%d)' % (info[0], self.txn))
            if info[0] > self.txn:
                self.meta = info[1]
                dataUpd = self.getData()
                dataAll = []
                for t in self.funcMerge(dataUpd, data):
                    dataAll.append(t)

                data    = dataAll

            self.setData(data)
            self.txn    = info[0] + 1
            self.setInfo((self.txn, self.meta))
            return data
        finally:
            self.unlock()

    def repack(self, data):
        self.lock()

        try:
            info = self.getInfo()
            if info[0] < self.txn: raise RuntimeError('save: current txn(%d) is less than self.txn(%d)' % (info[0], self.txn))
            if info[0] > self.txn:
                dataUpd = self.getData()
                dataAll = []
                for t in self.funcMerge(dataUpd, data):
                    dataAll.append(t)
                data    = dataAll

            arc = self.getArc()
            arcNew  = []
            dataNew = []
            for t in self.funcMerge(data, arc):
                if self.funcShouldArchive(t):
                    arcNew.append(t)
                else:
                    dataNew.append(t)

            self.setArc(arcNew)
            self.setData(dataNew)
            self.txn    = info[0] + 1
            self.meta   = info[1]
            self.setInfo((self.txn, self.meta))
            return dataNew
        finally:
            self.unlock()


##########################
if __name__ == "__main__":
    import sys
    if len(sys.argv) != 2: print "Usage: %s <storage_root>" % (sys.argv[0])
    g_root = sys.argv[1]
    
    def case1():
        f1 = File(g_root, 'test_obj')
        f1.lock()
        if not f1.isLocked(): raise RuntimeError('case1: failed 1')
        f1.unlock()

        f2 = File(g_root, 'test_obj')
        if f2.isLocked(): raise RuntimeError('case1: failed 2')
        f2.lock()
        if not f1.isLocked(): raise RuntimeError('case1: failed 3')
        f2.unlock()

    def case2():
        f = File(g_root, 'test_obj')
        f.destroy()
        d = f.load()
        if d != []: raise RuntimeError('case2: failed 1')
        if f.txn != 0: raise RuntimeError('case2: failed 2')
        if f.meta != {}: raise RuntimeError('case2: failed 3')

    def case3():
        f = File(g_root, 'test_obj')
        f.destroy()
        f.meta = 5
        f.reset()
        f.save([1, 2, 3])
        f.save([4, 5])
        if f.txn != 2: raise RuntimeError('case3: failed 1')
        if f.meta != 5: raise RuntimeError('case3: failed 2')

        j = File(g_root, 'test_obj')
        d = j.load()
        if j.txn != 2: raise RuntimeError('case3: failed 3')
        if j.meta != 5: raise RuntimeError('case3: failed 4')
        if d != [4, 5]: raise RuntimeError('case3: failed 5')

        f.destroy()

    case1()
    case2()
    case3()
